package com.one.blocks.security.authc.password;

import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.one.blocks.security.authc.AbstractAuthenticationProvider;
import com.one.blocks.security.config.properties.SecurityProperties;
import com.one.blocks.security.enums.SecurityExceptionEnum;
import com.one.blocks.security.exception.AuthcException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Optional;

/**
 * @author <a href="mailto:idler41@163.con">linfuxin</a> created on 2023-08-26 15:49:30
 */
@Slf4j
public class PasswordAuthenticationProvider extends AbstractAuthenticationProvider {

    protected final UserDetailsService userDetailsService;
    protected final PasswordEncoder passwordEncoder;

    protected final RSA rsa;

    public PasswordAuthenticationProvider(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService, SecurityProperties securityProperties) {
        super();
        this.passwordEncoder = passwordEncoder;
        this.userDetailsService = userDetailsService;

        String privateKey = Optional.ofNullable(securityProperties.getRsa())
                .filter(SecurityProperties.Rsa::isEnable)
                .map(SecurityProperties.Rsa::getPrivateKey).orElse(null);
        rsa = privateKey == null ? null : new RSA(privateKey, null);
    }

    public PasswordAuthenticationProvider(
            PasswordEncoder passwordEncoder, UserDetailsService userDetailsService,
            UserDetailsChecker preAuthenticationChecks, UserDetailsChecker postAuthenticationChecks,
            SecurityProperties securityProperties) {
        super(preAuthenticationChecks, postAuthenticationChecks);
        this.passwordEncoder = passwordEncoder;
        this.userDetailsService = userDetailsService;

        String privateKey = Optional.ofNullable(securityProperties.getRsa())
                .filter(SecurityProperties.Rsa::isEnable)
                .map(SecurityProperties.Rsa::getPrivateKey).orElse(null);
        rsa = privateKey == null ? null : new RSA(privateKey, null);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return PasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    @Override
    protected UserDetails retrieveUser(Authentication authentication) {
        return userDetailsService.loadUserByUsername((String) authentication.getPrincipal());
    }

    @Override
    protected Authentication buildSuccessAuthentication(Authentication authentication, UserDetails userDetails) {
        authentication.setAuthenticated(true);
        ((PasswordAuthenticationToken) authentication).setDetails(userDetails);
        return authentication;
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails loadedUser, Authentication authentication) {
        final String password = (String) authentication.getCredentials();
        if (password == null) {
            if (log.isDebugEnabled()) {
                log.debug("Authentication failed: no credentials provided");
            }
            throw new AuthcException(SecurityExceptionEnum.NO_CREDENTIALS);
        }

        String rawPassword;
        try {
            rawPassword = rsa.decryptStr(password, KeyType.PrivateKey);
        } catch (RuntimeException e) {
            throw new AuthcException(SecurityExceptionEnum.ILLEGAL_CREDENTIALS);
        }

        if (!passwordEncoder.matches(rawPassword, loadedUser.getPassword())) {
            throw new AuthcException(SecurityExceptionEnum.BAD_CREDENTIALS);
        }
    }

    public static void main(String[] args) {
        String privateKey = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANqpjAKuHZ/uGKm8q26fjcKHZTVMT7rbkJTKNKGjzaZ/B4PkcVBu3Ca6IzVt8+EdaskbZQah4Qjb1fGV+4s/w+ySa6bESR/8dVwge1TXTD591zA9Vnao6gVYP0qM8QECwhKyUZKo6cRbjJuDPP4PIBBo7tIMINCi2bHVUtRgFHBBAgMBAAECgYEAp3RCWecXntvLHWn0a3JbMMdZlZiMUpBFR/0Wk1cLbd9IVRRERxGvHSVdQ1Sh+Rr7JWoKUyrzWEJqtGlkyv4ZPo8/5lZV9RmBOSETUyOzyxWXdGrQwoATNhyxqz/TKcpag5qfr67f9CgaeOjPwa/tLsoiaYkve6aWbAMQXpwwdcUCQQD1ZLL/NEaErw2vcRCuCWUap55+01dRtoYEQhjGp/SuPRJKlFJQoIv8DyZbxyjMpydZDQndVpYSF1oVhPGfWEzjAkEA5B0Q4jCTJFNBpwyfJxvlZH/B9wrlmKJ5B3gbVDC62o3/3SVpXKtc7KohMeH8dIgLsb33Zac34ITLX/2J0FpbiwJAWQpk+L0bKWHrcVFxX/UAvp87n4FIGdpE3p6tJsgivGZkkLMo+yCGKrvEi2DEqR2t5V4Zyjp00bOveEd49PyxSQJBAJrKGmXSHlac5ETtrKWGKW+7vavcShX70fK+jpTfiDu79a5jr45BfgneHC5UMdIFayIOTO0+vgztL3AQhE7MsacCQQDhzHvAr7PA9KQFQ63lpkW+8laMJZToHHm8Qmfegft5lqQF/5XUAT4Y3iBUH8JqoZrIwpYxiIWzn0JQyKqPvhaG";
        String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaqYwCrh2f7hipvKtun43Ch2U1TE+625CUyjSho82mfweD5HFQbtwmuiM1bfPhHWrJG2UGoeEI29XxlfuLP8PskmumxEkf/HVcIHtU10w+fdcwPVZ2qOoFWD9KjPEBAsISslGSqOnEW4ybgzz+DyAQaO7SDCDQotmx1VLUYBRwQQIDAQAB";
        RSA test = new RSA(privateKey, publicKey);
        // RTSuP1WYvtS0gMjmnKGcQyA06hlqjKGT+xs7fLlqJPhcVpxYci/h7oY905ksCUMA+o0oyE3Nj/W8U13istLJmTUImn4MbI3YZVfLPCqgJTwpVxTY1KZLxcaxffLsHu27YVKEJ0NdeaVeDam/eRsrSBp9IyD6CErHoibFzMfPG04=
        String password = test.encryptBase64("123456", KeyType.PublicKey);
        String rawPassword = test.decryptStr(password, KeyType.PrivateKey);
        System.out.println(password);
        System.out.println(rawPassword);
    }
}
